/*
 * Conditions Of Use 
 * 
 * This software was developed by employees of the National Institute of
 * Standards and Technology (NIST), an agency of the Federal Government.
 * Pursuant to title 15 Untied States Code Section 105, works of NIST
 * employees are not subject to copyright protection in the United States
 * and are considered to be in the public domain.  As a result, a formal
 * license is not needed to use the software.
 * 
 * This software is provided by NIST as a service and is expressly
 * provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
 * AND DATA ACCURACY.  NIST does not warrant or make any representations
 * regarding the use of the software or the results thereof, including but
 * not limited to the correctness, accuracy, reliability or usefulness of
 * the software.
 * 
 * Permission to use this software is contingent upon your acceptance
 * of the terms of this agreement
 *  
 * .
 * 
 */
/******************************************************************************
 * Product of NIST/ITL Advanced Networking Technologies Division (ANTD)       *
 ******************************************************************************/
package gov.nist.javax.sip.parser;

/*
 * 
 * Lamine Brahimi and Yann Duponchel (IBM Zurich) noticed that the parser was
 * blocking so I threw out some cool pipelining which ran fast but only worked
 * when the phase of the moon matched its mood. Now things are serialized and
 * life goes slower but more reliably.
 * 
 */
import gov.nist.core.*;
import gov.nist.javax.sip.message.*;
import gov.nist.javax.sip.header.*;
import java.text.ParseException;
import java.io.*;

/**
 * This implements a pipelined message parser suitable for use with a stream -
 * oriented input such as TCP. The client uses this class by instatiating with
 * an input stream from which input is read and fed to a message parser. It
 * keeps reading from the input stream and process messages in a never ending
 * interpreter loop. The message listener interface gets called for processing
 * messages or for processing errors. The payload specified by the
 * content-length header is read directly from the input stream. This can be
 * accessed from the SIPMessage using the getContent and getContentBytes methods
 * provided by the SIPMessage class.
 * 
 * @version 1.2 $Revision: 1.21 $ $Date: 2008/05/24 04:10:01 $
 * 
 * @author M. Ranganathan
 * 
 * @see SIPMessageListener
 */
public final class PipelinedMsgParser implements Runnable {

	

	/**
	 * The message listener that is registered with this parser. (The message
	 * listener has methods that can process correct and erroneous messages.)
	 */
	protected SIPMessageListener sipMessageListener;
	private Thread mythread; // Preprocessor thread
	//private byte[] messageBody;
	//private boolean errorFlag;
	private Pipeline rawInputStream;
	private int maxMessageSize;
	private int sizeCounter;
	//private int messageSize;

	/**
	 * default constructor.
	 */
	protected PipelinedMsgParser() {
		super();

	}

	private static int uid = 0;

	private static synchronized int getNewUid() {
		return uid++;
	}

	/**
	 * Constructor when we are given a message listener and an input stream
	 * (could be a TCP connection or a file)
	 * 
	 * @param sipMessageListener
	 *            Message listener which has methods that get called back from
	 *            the parser when a parse is complete
	 * @param in
	 *            Input stream from which to read the input.
	 * @param debug
	 *            Enable/disable tracing or lexical analyser switch.
	 */
	public PipelinedMsgParser(SIPMessageListener sipMessageListener,
			Pipeline in, boolean debug, int maxMessageSize) {
		this();
		this.sipMessageListener = sipMessageListener;
		rawInputStream = in;
		this.maxMessageSize = maxMessageSize;
		mythread = new Thread(this);
		mythread.setName("PipelineThread-" + getNewUid());

	}

	/**
	 * This is the constructor for the pipelined parser.
	 * 
	 * @param mhandler
	 *            a SIPMessageListener implementation that provides the message
	 *            handlers to handle correctly and incorrectly parsed messages.
	 * @param in
	 *            An input stream to read messages from.
	 */

	public PipelinedMsgParser(SIPMessageListener mhandler, Pipeline in,
			int maxMsgSize) {
		this(mhandler, in, false, maxMsgSize);
	}

	/**
	 * This is the constructor for the pipelined parser.
	 * 
	 * @param in -
	 *            An input stream to read messages from.
	 */

	public PipelinedMsgParser(Pipeline in) {
		this(null, in, false, 0);
	}

	/**
	 * Start reading and processing input.
	 */
	public void processInput() {
		mythread.start();
	}

	/**
	 * Create a new pipelined parser from an existing one.
	 * 
	 * @return A new pipelined parser that reads from the same input stream.
	 */
	protected Object clone() {
		PipelinedMsgParser p = new PipelinedMsgParser();

		p.rawInputStream = this.rawInputStream;
		p.sipMessageListener = this.sipMessageListener;
		Thread mythread = new Thread(p);
		mythread.setName("PipelineThread");
		return p;
	}

	/**
	 * Add a class that implements a SIPMessageListener interface whose methods
	 * get called * on successful parse and error conditons.
	 * 
	 * @param mlistener
	 *            a SIPMessageListener implementation that can react to correct
	 *            and incorrect pars.
	 */

	public void setMessageListener(SIPMessageListener mlistener) {
		sipMessageListener = mlistener;
	}

	/**
	 * read a line of input (I cannot use buffered reader because we may need to
	 * switch encodings mid-stream!
	 */
	private String readLine(InputStream inputStream) throws IOException {
		StringBuffer retval = new StringBuffer("");
		while (true) {
			char ch;
			int i = inputStream.read();
			if (i == -1) {
				throw new IOException("End of stream");
			} else
				ch = (char) i;
			// reduce the available read size by 1 ("size" of a char).
			if (this.maxMessageSize > 0) {
				this.sizeCounter--;
				if (this.sizeCounter <= 0)
					throw new IOException("Max size exceeded!");
			}
			if (ch != '\r')
				retval.append(ch);
			if (ch == '\n') {
				break;
			}
		}
		return retval.toString();
	}

	/**
	 * This is input reading thread for the pipelined parser. You feed it input
	 * through the input stream (see the constructor) and it calls back an event
	 * listener interface for message processing or error. It cleans up the
	 * input - dealing with things like line continuation
	 */
	public void run() {

		Pipeline inputStream = this.rawInputStream;
		// inputStream = new MyFilterInputStream(this.rawInputStream);
		// I cannot use buffered reader here because we may need to switch
		// encodings to read the message body.
		try {
			while (true) {
				this.sizeCounter = this.maxMessageSize;
				// this.messageSize = 0;
				StringBuffer inputBuffer = new StringBuffer();

				if (Debug.parserDebug)
					Debug.println("Starting parse!");

				String line1;
				String line2 = null;

				while (true) {
					try {
						line1 = readLine(inputStream);
						// ignore blank lines.
						if (line1.equals("\n")) {
							if (Debug.parserDebug)
								Debug.println("Discarding " + line1);
							continue;
						} else
							break;
					} catch (IOException ex) {
						Debug.printStackTrace(ex);
						this.rawInputStream.stopTimer();
						return;

					}
				}

				inputBuffer.append(line1);
				// Guard against bad guys.
				this.rawInputStream.startTimer();

				while (true) {
					try {
						line2 = readLine(inputStream);
						inputBuffer.append(line2);
						if (line2.trim().equals(""))
							break;
					} catch (IOException ex) {
						this.rawInputStream.stopTimer();
						Debug.printStackTrace(ex);
						return;

					}
				}

				// Stop the timer that will kill the read.
				this.rawInputStream.stopTimer();
				inputBuffer.append(line2);
				StringMsgParser smp = new StringMsgParser(sipMessageListener);
				smp.readBody = false;
				SIPMessage sipMessage = null;

				try {
					sipMessage = smp.parseSIPMessage(inputBuffer.toString());
					if (sipMessage == null) {
						this.rawInputStream.stopTimer();
						continue;
					}
				} catch (ParseException ex) {
					// Just ignore the parse exception.
					continue;
				}

				if (Debug.parserDebug)
					Debug.println("Completed parsing message");
				ContentLength cl = (ContentLength) sipMessage
						.getContentLength();
				int contentLength = 0;
				if (cl != null) {
					contentLength = cl.getContentLength();
				} else {
					contentLength = 0;
				}

				if (Debug.parserDebug) {
					Debug.println("contentLength " + contentLength);
					Debug.println("sizeCounter " + this.sizeCounter);
					Debug.println("maxMessageSize " + this.maxMessageSize);
				}

				if (contentLength == 0) {
					sipMessage.removeContent();
				} else if (maxMessageSize == 0
						|| contentLength < this.sizeCounter) {
					byte[] message_body = new byte[contentLength];
					int nread = 0;
					while (nread < contentLength) {
						// Start my starvation timer.
						// This ensures that the other end
						// writes at least some data in
						// or we will close the pipe from
						// him. This prevents DOS attack
						// that takes up all our connections.
						this.rawInputStream.startTimer();
						try {

							int readlength = inputStream.read(message_body,
									nread, contentLength - nread);
							if (readlength > 0) {
								nread += readlength;
							} else {
								break;
							}
						} catch (IOException ex) {
							ex.printStackTrace();
							break;
						} finally {
							// Stop my starvation timer.
							this.rawInputStream.stopTimer();
						}
					}
					sipMessage.setMessageContent(message_body);
				}
				// Content length too large - process the message and
				// return error from there.
				if (sipMessageListener != null) {
					try {
						sipMessageListener.processMessage(sipMessage);
					} catch (Exception ex) {
						// fatal error in processing - close the
						// connection.
						break;
					}
				}
			}
		} finally {
			try {
				inputStream.close();
			} catch (IOException e) {
				InternalErrorHandler.handleException(e);
			}
		}
	}
	
	public void close() {
		try {
			this.rawInputStream.close();
		} catch (IOException ex) {
			// Ignore.
		}
	}
}
/*
 * $Log: PipelinedMsgParser.java,v $
 * Revision 1.21  2008/05/24 04:10:01  mranga
 *
 * Issue number:   158
 * Obtained from:
 * Submitted by:
 * Reviewed by:   mranga
 *
 * Deliver tx timeout for Canceled INVITE. Fix pipeline thread exit.
 *
 * Revision 1.20  2008/05/22 19:38:07  jbemmel
 * Fix for issue 149: the logic wasn't always closing the internal socket pipe,
 * causing the pipe reader thread to block indefinitely
 *
 * Repeatedly starting/stopping the stack then gives hanging threads
 * Revision 1.19 2007/01/28 13:06:21 mranga
 * Issue number: 99 Obtained from: Submitted by: Reviewed by: mranga
 * 
 * Fixed PRACK handling null pointer exception (for proxy case) and cleanup of
 * unused variables.
 * 
 * CVS: ----------------------------------------------------------------------
 * CVS: Issue number: CVS: If this change addresses one or more issues, CVS:
 * then enter the issue number(s) here. CVS: Obtained from: CVS: If this change
 * has been taken from another system, CVS: then name the system in this line,
 * otherwise delete it. CVS: Submitted by: CVS: If this code has been
 * contributed to the project by someone else; i.e., CVS: they sent us a patch
 * or a set of diffs, then include their name/email CVS: address here. If this
 * is your work then delete this line. CVS: Reviewed by: CVS: If we are doing
 * pre-commit code reviews and someone else has CVS: reviewed your changes,
 * include their name(s) here. CVS: If you have not had it reviewed then delete
 * this line.
 * 
 * Revision 1.18 2006/07/13 09:02:10 mranga Issue number: Obtained from:
 * Submitted by: jeroen van bemmel Reviewed by: mranga Moved some changes from
 * jain-sip-1.2 to java.net
 * 
 * CVS: ----------------------------------------------------------------------
 * CVS: Issue number: CVS: If this change addresses one or more issues, CVS:
 * then enter the issue number(s) here. CVS: Obtained from: CVS: If this change
 * has been taken from another system, CVS: then name the system in this line,
 * otherwise delete it. CVS: Submitted by: CVS: If this code has been
 * contributed to the project by someone else; i.e., CVS: they sent us a patch
 * or a set of diffs, then include their name/email CVS: address here. If this
 * is your work then delete this line. CVS: Reviewed by: CVS: If we are doing
 * pre-commit code reviews and someone else has CVS: reviewed your changes,
 * include their name(s) here. CVS: If you have not had it reviewed then delete
 * this line.
 * 
 * Revision 1.4 2006/06/19 06:47:27 mranga javadoc fixups
 * 
 * Revision 1.3 2006/06/17 10:18:14 mranga Added some synchronization to the
 * sequence number checking. Small javadoc fixups
 * 
 * Revision 1.2 2006/06/16 15:26:28 mranga Added NIST disclaimer to all public
 * domain files. Clean up some javadoc. Fixed a leak
 * 
 * Revision 1.1.1.1 2005/10/04 17:12:35 mranga
 * 
 * Import
 * 
 * 
 * Revision 1.16 2004/11/30 23:28:14 mranga Issue number: 44 Submitted by: Rob
 * Daugherty Reviewed by: M. Ranganathan
 * 
 * TCP Pipelining truncates content when other end of pipe is closed.
 * 
 * Revision 1.15 2004/05/30 18:55:56 mranga Reviewed by: mranga Move to timers
 * and eliminate the Transaction scanner Thread to improve scalability and
 * reduce cpu usage.
 * 
 * Revision 1.14 2004/05/16 14:13:22 mranga Reviewed by: mranga Fixed the
 * use-count issue reported by Peter Parnes. Added property to prevent against
 * content-length dos attacks.
 * 
 * Revision 1.13 2004/03/19 04:22:22 mranga Reviewed by: mranga Added IO Pacing
 * for long writes - split write into chunks and flush after each chunk to avoid
 * socket back pressure.
 * 
 * Revision 1.12 2004/03/18 22:01:19 mranga Reviewed by: mranga Get rid of the
 * PipedInputStream from pipelined parser to avoid a copy.
 * 
 * Revision 1.11 2004/03/07 22:25:23 mranga Reviewed by: mranga Added a new
 * configuration parameter that instructs the stack to drop a server connection
 * after server transaction termination set
 * gov.nist.javax.sip.CACHE_SERVER_CONNECTIONS=false for this Default behavior
 * is true.
 * 
 * Revision 1.10 2004/02/29 15:32:58 mranga Reviewed by: mranga bug fixes on
 * limiting the max message size.
 * 
 * Revision 1.9 2004/02/29 00:46:34 mranga Reviewed by: mranga Added new
 * configuration property to limit max message size for TCP transport. The
 * property is gov.nist.javax.sip.MAX_MESSAGE_SIZE
 * 
 * Revision 1.8 2004/02/25 21:43:03 mranga Reviewed by: mranga Added a couple of
 * todo's and removed some debug printlns that could slow code down by a bit.
 * 
 * Revision 1.7 2004/02/25 20:52:46 mranga Reviewed by: mranga Fix TCP transport
 * so messages in excess of 8192 bytes are accepted.
 * 
 * Revision 1.6 2004/01/22 18:39:41 mranga Reviewed by: M. Ranganathan Moved the
 * ifdef SIMULATION and associated tags to the first column so Prep preprocessor
 * can deal with them.
 * 
 * Revision 1.5 2004/01/22 14:23:45 mranga Reviewed by: mranga Fixed some minor
 * formatting issues.
 * 
 * Revision 1.4 2004/01/22 13:26:31 sverker Issue number: Obtained from:
 * Submitted by: sverker Reviewed by: mranga
 * 
 * Major reformat of code to conform with style guide. Resolved compiler and
 * javadoc warnings. Added CVS tags.
 * 
 * CVS: ----------------------------------------------------------------------
 * CVS: Issue number: CVS: If this change addresses one or more issues, CVS:
 * then enter the issue number(s) here. CVS: Obtained from: CVS: If this change
 * has been taken from another system, CVS: then name the system in this line,
 * otherwise delete it. CVS: Submitted by: CVS: If this code has been
 * contributed to the project by someone else; i.e., CVS: they sent us a patch
 * or a set of diffs, then include their name/email CVS: address here. If this
 * is your work then delete this line. CVS: Reviewed by: CVS: If we are doing
 * pre-commit code reviews and someone else has CVS: reviewed your changes,
 * include their name(s) here. CVS: If you have not had it reviewed then delete
 * this line.
 * 
 */
